package com.sdnc.common.redis;

import com.sdnc.common.annotation.CacheEvict;
import com.sdnc.common.annotation.CachePut;
import com.sdnc.common.annotation.Cacheable;
import com.sdnc.common.kits.StrKit;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 缓存拦截，用于注册方法信息
 *
 * @author Ray
 */
@Aspect
@Component
@RequiredArgsConstructor
public class CacheAspect {

	private static final String CACHE_KEY_ERROR_MESSAGE = "缓存Key %s 不能为NULL";
	private static final String CACHE_NAME_ERROR_MESSAGE = "缓存名称不能为NULL";
	private final RedisCache<Object> cache;

	@Pointcut("@annotation(com.sdnc.common.annotation.Cacheable)")
	public void cacheablePointcut() {
	}

	@Pointcut("@annotation(com.sdnc.common.annotation.CacheEvict)")
	public void cacheEvictPointcut() {
	}

	@Pointcut("@annotation(com.sdnc.common.annotation.CachePut)")
	public void cachePutPointcut() {
	}

	@Around("cacheablePointcut()")
	public Object cacheablePointcut(ProceedingJoinPoint joinPoint) throws Throwable {
		// 获取method
		Method method = this.getSpecificmethod(joinPoint);
		// 获取注解
		Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
		String key = generateKey(joinPoint, cacheable.cacheNames(), cacheable.key());
		Object result = cache.getValue(key);
		if (result == null) {
			result = joinPoint.proceed();
			if (!key.endsWith(":") && result != null) {
				cache.putValue(key, result, cacheable.timeout(), cacheable.timeUnit());
			}
		}

		return result;
	}

	/**
	 * 获取Method
	 *
	 * @param pjp
	 * 		ProceedingJoinPoint
	 * @return {@link Method}
	 */
	private Method getSpecificmethod(ProceedingJoinPoint pjp) {
		MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
		Method method = methodSignature.getMethod();
		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
		if (targetClass == null && pjp.getTarget() != null) {
			targetClass = pjp.getTarget().getClass();
		}
		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);

		return BridgeMethodResolver.findBridgedMethod(specificMethod);
	}

	/**
	 * 解析表达式，获取注解上的key属性值
	 *
	 * @return Object
	 */
	private String generateKey(ProceedingJoinPoint joinPoint, Object[] cacheNames, String key) throws IOException {
		StringBuilder keys = new StringBuilder();
		keys.append(cacheNames[0]);
		if (StrKit.notBlank(key)) {
			//初始化代码
			StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
			Configuration cfg = Configuration.defaultConfiguration();
			GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
			//获取模板
			Template template = gt.getTemplate("${" + key + "}");
			Object[] args = joinPoint.getArgs();
			MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
			String[] argNames = methodSignature.getParameterNames();
			for (int i = 0, length = argNames.length; i < length; ++i) {
				template.binding(argNames[i], args[i]);
			}
			keys.append(":").append(template.render());
		}

		return keys.toString();
	}

	@Around("cacheEvictPointcut()")
	public Object cacheEvictPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
		// 获取method
		Method method = this.getSpecificmethod(joinPoint);
		// 获取注解
		CacheEvict cacheEvict = AnnotationUtils.findAnnotation(method, CacheEvict.class);
		String key = generateKey(joinPoint, cacheEvict.cacheNames(), cacheEvict.key());
		if (!key.endsWith(":")) {
			cache.unlink(key);
		}

		return joinPoint.proceed();
	}

	@Around("cachePutPointcut()")
	public Object cachePutPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
		// 获取method
		Method method = this.getSpecificmethod(joinPoint);
		// 获取注解
		CachePut cachePut = AnnotationUtils.findAnnotation(method, CachePut.class);
		String key = generateKey(joinPoint, cachePut.cacheNames(), cachePut.key());
		Object result = joinPoint.proceed();
		List<Object> list = (List<Object>) cache.getValue(key);
		if (!list.isEmpty()) {
			list.add(result);
			long timeout = cache.getExpire(key, TimeUnit.MINUTES);
			if (!key.endsWith(":")) {
				cache.putValue(key, list, timeout, TimeUnit.MINUTES);
			}
		}

		return result;
	}

}